// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "intmath.h"
#include "zencore.h"

#include <stdint.h>
#include <string.h>
#include <charconv>
#include <codecvt>
#include <compare>
#include <concepts>
#include <optional>
#include <span>
#include <string_view>

#include <type_traits>

namespace zen {

//////////////////////////////////////////////////////////////////////////

inline bool
StringEquals(const char8_t* s1, const char* s2)
{
	return strcmp(reinterpret_cast<const char*>(s1), s2) == 0;
}

inline bool
StringEquals(const char* s1, const char* s2)
{
	return strcmp(s1, s2) == 0;
}

inline size_t
StringLength(const char* str)
{
	return strlen(str);
}

inline bool
StringEquals(const wchar_t* s1, const wchar_t* s2)
{
	return wcscmp(s1, s2) == 0;
}

inline size_t
StringLength(const wchar_t* str)
{
	return wcslen(str);
}

//////////////////////////////////////////////////////////////////////////
// File name helpers
//

ZENCORE_API const char* FilepathFindExtension(const std::string_view& path, const char* extensionToMatch = nullptr);

//////////////////////////////////////////////////////////////////////////
// Text formatting of numbers
//

ZENCORE_API bool ToString(std::span<char> Buffer, uint64_t Num);
ZENCORE_API bool ToString(std::span<char> Buffer, int64_t Num);

struct TextNumBase
{
	inline const char* c_str() const { return m_Buffer; }
	inline			   operator std::string_view() const { return std::string_view(m_Buffer); }

protected:
	char m_Buffer[24];
};

struct IntNum : public TextNumBase
{
	inline IntNum(UnsignedIntegral auto Number) { ToString(m_Buffer, uint64_t(Number)); }
	inline IntNum(SignedIntegral auto Number) { ToString(m_Buffer, int64_t(Number)); }
};

//////////////////////////////////////////////////////////////////////////
//
// Quick-and-dirty string builder. Good enough for me, but contains traps
// and not-quite-ideal behaviour especially when mixing character types etc
//

template<typename C>
class StringBuilderImpl
{
public:
	StringBuilderImpl() = default;
	ZENCORE_API ~StringBuilderImpl();

	StringBuilderImpl(const StringBuilderImpl&)	 = delete;
	StringBuilderImpl(const StringBuilderImpl&&) = delete;
	const StringBuilderImpl& operator=(const StringBuilderImpl&) = delete;
	const StringBuilderImpl& operator=(const StringBuilderImpl&&) = delete;

	inline size_t AddUninitialized(size_t Count)
	{
		EnsureCapacity(Count);
		const size_t OldCount = Size();
		m_CurPos += Count;
		return OldCount;
	}

	StringBuilderImpl& Append(C OneChar)
	{
		EnsureCapacity(1);

		*m_CurPos++ = OneChar;

		return *this;
	}

	inline StringBuilderImpl& AppendAscii(const std::string_view& String)
	{
		const size_t len = String.size();

		EnsureCapacity(len);

		for (size_t i = 0; i < len; ++i)
			m_CurPos[i] = String[i];

		m_CurPos += len;

		return *this;
	}

	inline StringBuilderImpl& AppendAscii(const std::u8string_view& String)
	{
		const size_t len = String.size();

		EnsureCapacity(len);

		for (size_t i = 0; i < len; ++i)
			m_CurPos[i] = String[i];

		m_CurPos += len;

		return *this;
	}

	inline StringBuilderImpl& AppendAscii(const char* NulTerminatedString)
	{
		size_t StringLen = StringLength(NulTerminatedString);

		return AppendAscii({NulTerminatedString, StringLen});
	}

	inline StringBuilderImpl& Append(const char8_t* NulTerminatedString)
	{
		// This is super hacky and not fully functional - needs better
		// solution
		if constexpr (sizeof(C) == 1)
		{
			size_t len = StringLength((const char*)NulTerminatedString);

			EnsureCapacity(len);

			for (size_t i = 0; i < len; ++i)
				m_CurPos[i] = C(NulTerminatedString[i]);

			m_CurPos += len;
		}
		else
		{
			ZEN_NOT_IMPLEMENTED();
		}

		return *this;
	}

	inline StringBuilderImpl& AppendAsciiRange(const char* BeginString, const char* EndString)
	{
		EnsureCapacity(EndString - BeginString);

		while (BeginString != EndString)
			*m_CurPos++ = *BeginString++;

		return *this;
	}

	inline StringBuilderImpl& Append(const C* NulTerminatedString)
	{
		size_t Len = StringLength(NulTerminatedString);

		EnsureCapacity(Len);
		memcpy(m_CurPos, NulTerminatedString, Len * sizeof(C));
		m_CurPos += Len;

		return *this;
	}

	inline StringBuilderImpl& Append(const C* NulTerminatedString, size_t MaxChars)
	{
		size_t len = Min(MaxChars, StringLength(NulTerminatedString));

		EnsureCapacity(len);
		memcpy(m_CurPos, NulTerminatedString, len * sizeof(C));
		m_CurPos += len;

		return *this;
	}

	inline StringBuilderImpl& AppendRange(const C* BeginString, const C* EndString)
	{
		size_t Len = EndString - BeginString;

		EnsureCapacity(Len);
		memcpy(m_CurPos, BeginString, Len * sizeof(C));
		m_CurPos += Len;

		return *this;
	}

	inline StringBuilderImpl& Append(const std::basic_string_view<C>& String)
	{
		return AppendRange(String.data(), String.data() + String.size());
	}

	inline StringBuilderImpl& AppendBool(bool v)
	{
		// This is a method instead of a << operator overload as the latter can
		// easily get called with non-bool types like pointers. It is a very
		// subtle behaviour that can cause bugs.
		using namespace std::literals;
		if (v)
		{
			return AppendAscii("true"sv);
		}
		return AppendAscii("false"sv);
	}

	inline void RemoveSuffix(uint32_t Count)
	{
		ZEN_ASSERT(Count <= Size());
		m_CurPos -= Count;
	}

	inline const C* c_str() const
	{
		EnsureNulTerminated();
		return m_Base;
	}

	inline C* Data()
	{
		EnsureNulTerminated();
		return m_Base;
	}

	inline const C* Data() const
	{
		EnsureNulTerminated();
		return m_Base;
	}

	inline size_t Size() const { return m_CurPos - m_Base; }
	inline bool	  IsDynamic() const { return m_IsDynamic; }
	inline void	  Reset() { m_CurPos = m_Base; }

	inline StringBuilderImpl& operator<<(uint64_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(int64_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(uint32_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(int32_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(uint16_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(int16_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(uint8_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
	inline StringBuilderImpl& operator<<(int8_t n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}

#if defined(__clang__) && !defined(__APPLE__)
	/* UE Toolchain Clang has different types for int64_t and long long so an override is
	needed here. Without it, Clang can't disambiguate integer overloads */
	inline StringBuilderImpl& operator<<(long long n)
	{
		IntNum Str(n);
		return AppendAscii(Str);
	}
#endif

	inline StringBuilderImpl& operator<<(const char* str) { return AppendAscii(str); }
	inline StringBuilderImpl& operator<<(const std::string_view str) { return AppendAscii(str); }
	inline StringBuilderImpl& operator<<(const std::u8string_view str) { return AppendAscii(str); }

	inline void EnsureNulTerminated() const { *m_CurPos = '\0'; }

	typedef C char_type;  // allows this to be used with code targeting some std stream classes

protected:
	inline void Init(C* Base, size_t Capacity)
	{
		m_Base = m_CurPos = Base;
		m_End			  = Base + Capacity;
	}

	inline void EnsureCapacity(size_t ExtraRequired)
	{
		// precondition: we know the current buffer has enough capacity
		// for the existing string including NUL terminator

		if ((m_CurPos + ExtraRequired) < m_End)
			return;

		Extend(ExtraRequired);
	}

	ZENCORE_API void  Extend(size_t ExtraCapacity);
	ZENCORE_API void* AllocBuffer(size_t ByteCount);
	ZENCORE_API void  FreeBuffer(void* Buffer, size_t ByteCount);

	ZENCORE_API [[noreturn]] void Fail(const char* FailReason);	 // note: throws exception

	C*	 m_Base;
	C*	 m_CurPos;
	C*	 m_End;
	bool m_IsDynamic	= false;
	bool m_IsExtendable = false;
};

//////////////////////////////////////////////////////////////////////////

extern template class StringBuilderImpl<char>;

inline StringBuilderImpl<char>&
operator<<(StringBuilderImpl<char>& Builder, char Char)
{
	return Builder.Append(Char);
}

class StringBuilderBase : public StringBuilderImpl<char>
{
public:
	inline StringBuilderBase(char* bufferPointer, size_t bufferCapacity) { Init(bufferPointer, bufferCapacity); }
	inline ~StringBuilderBase() = default;

	// Note that we don't need a terminator for the string_view so we avoid calling data() here
	inline					operator std::string_view() const { return std::string_view(m_Base, m_CurPos - m_Base); }
	inline std::string_view ToView() const { return std::string_view(m_Base, m_CurPos - m_Base); }
	inline std::string		ToString() const { return std::string{Data(), Size()}; }

	inline void AppendCodepoint(uint32_t cp)
	{
		if (cp < 0x80)	// one octet
		{
			Append(static_cast<char8_t>(cp));
		}
		else if (cp < 0x800)
		{
			EnsureCapacity(2);	// two octets
			m_CurPos[0] = static_cast<char8_t>((cp >> 6) | 0xc0);
			m_CurPos[1] = static_cast<char8_t>((cp & 0x3f) | 0x80);
			m_CurPos += 2;
		}
		else if (cp < 0x10000)
		{
			EnsureCapacity(3);	// three octets
			m_CurPos[0] = static_cast<char8_t>((cp >> 12) | 0xe0);
			m_CurPos[1] = static_cast<char8_t>(((cp >> 6) & 0x3f) | 0x80);
			m_CurPos[2] = static_cast<char8_t>((cp & 0x3f) | 0x80);
			m_CurPos += 3;
		}
		else
		{
			EnsureCapacity(4);	// four octets
			m_CurPos[0] = static_cast<char8_t>((cp >> 18) | 0xf0);
			m_CurPos[1] = static_cast<char8_t>(((cp >> 12) & 0x3f) | 0x80);
			m_CurPos[2] = static_cast<char8_t>(((cp >> 6) & 0x3f) | 0x80);
			m_CurPos[3] = static_cast<char8_t>((cp & 0x3f) | 0x80);
			m_CurPos += 4;
		}
	}
};

template<size_t N>
class StringBuilder : public StringBuilderBase
{
public:
	inline StringBuilder() : StringBuilderBase(m_StringBuffer, sizeof m_StringBuffer) {}
	inline ~StringBuilder() = default;

private:
	char m_StringBuffer[N];
};

template<size_t N>
class ExtendableStringBuilder : public StringBuilderBase
{
public:
	inline ExtendableStringBuilder() : StringBuilderBase(m_StringBuffer, sizeof m_StringBuffer) { m_IsExtendable = true; }
	inline ~ExtendableStringBuilder() = default;

private:
	char m_StringBuffer[N];
};

template<size_t N>
class WriteToString : public ExtendableStringBuilder<N>
{
public:
	template<typename... ArgTypes>
	explicit WriteToString(ArgTypes&&... Args)
	{
		(*this << ... << std::forward<ArgTypes>(Args));
	}
};

//////////////////////////////////////////////////////////////////////////

extern template class StringBuilderImpl<wchar_t>;

class WideStringBuilderBase : public StringBuilderImpl<wchar_t>
{
public:
	inline WideStringBuilderBase(wchar_t* BufferPointer, size_t BufferCapacity) { Init(BufferPointer, BufferCapacity); }
	inline ~WideStringBuilderBase() = default;

	inline					 operator std::wstring_view() const { return std::wstring_view{Data(), Size()}; }
	inline std::wstring_view ToView() const { return std::wstring_view{Data(), Size()}; }
	inline std::wstring		 ToString() const { return std::wstring{Data(), Size()}; }

	inline StringBuilderImpl& operator<<(const std::wstring_view str) { return Append((const wchar_t*)str.data(), str.size()); }
	inline StringBuilderImpl& operator<<(const wchar_t* str) { return Append(str); }
	using StringBuilderImpl:: operator<<;
};

template<size_t N>
class WideStringBuilder : public WideStringBuilderBase
{
public:
	inline WideStringBuilder() : WideStringBuilderBase(m_Buffer, N) {}
	~WideStringBuilder() = default;

private:
	wchar_t m_Buffer[N];
};

template<size_t N>
class ExtendableWideStringBuilder : public WideStringBuilderBase
{
public:
	inline ExtendableWideStringBuilder() : WideStringBuilderBase(m_Buffer, N) { m_IsExtendable = true; }
	~ExtendableWideStringBuilder() = default;

private:
	wchar_t m_Buffer[N];
};

template<size_t N>
class WriteToWideString : public ExtendableWideStringBuilder<N>
{
public:
	template<typename... ArgTypes>
	explicit WriteToWideString(ArgTypes&&... Args)
	{
		(*this << ... << Forward<ArgTypes>(Args));
	}
};

//////////////////////////////////////////////////////////////////////////

void		 Utf8ToWide(const char8_t* str, WideStringBuilderBase& out);
void		 Utf8ToWide(const std::u8string_view& wstr, WideStringBuilderBase& out);
void		 Utf8ToWide(const std::string_view& wstr, WideStringBuilderBase& out);
std::wstring Utf8ToWide(const std::string_view& wstr);

void		WideToUtf8(const wchar_t* wstr, StringBuilderBase& out);
std::string WideToUtf8(const wchar_t* wstr);
void		WideToUtf8(const std::wstring_view& wstr, StringBuilderBase& out);
std::string WideToUtf8(const std::wstring_view Wstr);

inline uint8_t
Char2Nibble(char c)
{
	if (c >= '0' && c <= '9')
	{
		return uint8_t(c - '0');
	}
	if (c >= 'a' && c <= 'f')
	{
		return uint8_t(c - 'a' + 10);
	}
	if (c >= 'A' && c <= 'F')
	{
		return uint8_t(c - 'A' + 10);
	}
	return uint8_t(0xff);
};

static constexpr const char HexChars[] = "0123456789abcdef";

/// <summary>
/// Parse hex string into a byte buffer
/// </summary>
/// <param name="string">Input string</param>
/// <param name="characterCount">Number of characters in string</param>
/// <param name="outPtr">Pointer to output buffer</param>
/// <returns>true if the input consisted of all valid hexadecimal characters</returns>

inline bool
ParseHexBytes(const char* InputString, size_t CharacterCount, uint8_t* OutPtr)
{
	ZEN_ASSERT((CharacterCount & 1) == 0);

	uint8_t allBits = 0;

	while (CharacterCount)
	{
		uint8_t n0 = Char2Nibble(InputString[0]);
		uint8_t n1 = Char2Nibble(InputString[1]);

		allBits |= n0 | n1;

		*OutPtr = (n0 << 4) | n1;

		OutPtr += 1;
		InputString += 2;
		CharacterCount -= 2;
	}

	return (allBits & 0x80) == 0;
}

inline bool
ParseHexBytes(std::string_view InputString, uint8_t* OutPtr)
{
	return ParseHexBytes(InputString.data(), InputString.size(), OutPtr);
}

inline void
ToHexBytes(const uint8_t* InputData, size_t ByteCount, char* OutString)
{
	while (ByteCount--)
	{
		uint8_t byte = *InputData++;

		*OutString++ = HexChars[byte >> 4];
		*OutString++ = HexChars[byte & 15];
	}
}

inline bool
ParseHexNumber(const char* InputString, size_t CharacterCount, uint8_t* OutPtr)
{
	ZEN_ASSERT((CharacterCount & 1) == 0);

	uint8_t allBits = 0;

	// This assumes little-endian
	InputString += CharacterCount;
	while (CharacterCount)
	{
		InputString -= 2;
		uint8_t n0 = Char2Nibble(InputString[0]);
		uint8_t n1 = Char2Nibble(InputString[1]);

		allBits |= n0 | n1;

		*OutPtr = (n0 << 4) | n1;

		OutPtr += 1;
		CharacterCount -= 2;
	}

	return (allBits & 0x80) == 0;
}

inline bool
ParseHexNumber(std::string_view InputString, uint8_t* OutPtr)
{
	return ParseHexNumber(InputString.data(), InputString.size(), OutPtr);
}

inline void
ToHexNumber(const uint8_t* InputData, size_t ByteCount, char* OutString)
{
	InputData += ByteCount;
	while (ByteCount--)
	{
		uint8_t byte = *(--InputData);

		*OutString++ = HexChars[byte >> 4];
		*OutString++ = HexChars[byte & 15];
	}
}

/// <summary>
/// Generates a hex number from a pointer to an integer type, this formats the number in the correct order for a hexadecimal number
/// </summary>
/// <param name="Value">Integer value type</param>
/// <param name="outString">Output buffer where resulting string is written</param>
void
ToHexNumber(UnsignedIntegral auto Value, char* OutString)
{
	ToHexNumber((const uint8_t*)&Value, sizeof(Value), OutString);
	OutString[sizeof(Value) * 2] = 0;
}

/// <summary>
/// Parse hex number string into a value, this formats the number in the correct order for a hexadecimal number
/// </summary>
/// <param name="HexString">Input string</param>
/// <param name="OutValue">Pointer to output value</param>
/// <returns>true if the input consisted of all valid hexadecimal characters</returns>
bool
ParseHexNumber(const std::string_view HexString, UnsignedIntegral auto& OutValue)
{
	size_t ExpectedCharacterCount = sizeof(OutValue) * 2;
	if (HexString.size() != ExpectedCharacterCount)
	{
		return false;
	}
	return ParseHexNumber(HexString.data(), ExpectedCharacterCount, (uint8_t*)&OutValue);
}

//////////////////////////////////////////////////////////////////////////
// Format numbers for humans
//

ZENCORE_API size_t NiceNumToBuffer(uint64_t Num, std::span<char> Buffer);
ZENCORE_API size_t NiceBytesToBuffer(uint64_t Num, std::span<char> Buffer);
ZENCORE_API size_t NiceByteRateToBuffer(uint64_t Num, uint64_t ms, std::span<char> Buffer);
ZENCORE_API size_t NiceLatencyNsToBuffer(uint64_t NanoSeconds, std::span<char> Buffer);
ZENCORE_API size_t NiceTimeSpanMsToBuffer(uint64_t Milliseconds, std::span<char> Buffer);

struct NiceBase
{
	inline const char* c_str() const { return m_Buffer; }
	inline			   operator std::string_view() const { return std::string_view(m_Buffer); }

protected:
	char m_Buffer[16];
};

struct NiceNum : public NiceBase
{
	inline NiceNum(uint64_t Num) { NiceNumToBuffer(Num, m_Buffer); }
};

struct NiceBytes : public NiceBase
{
	inline NiceBytes(uint64_t Num) { NiceBytesToBuffer(Num, m_Buffer); }
};

struct NiceByteRate : public NiceBase
{
	inline NiceByteRate(uint64_t Bytes, uint64_t TimeMilliseconds) { NiceByteRateToBuffer(Bytes, TimeMilliseconds, m_Buffer); }
};

struct NiceLatencyNs : public NiceBase
{
	inline NiceLatencyNs(uint64_t Milliseconds) { NiceLatencyNsToBuffer(Milliseconds, m_Buffer); }
};

struct NiceTimeSpanMs : public NiceBase
{
	inline NiceTimeSpanMs(uint64_t Milliseconds) { NiceTimeSpanMsToBuffer(Milliseconds, m_Buffer); }
};

//////////////////////////////////////////////////////////////////////////

inline std::string
NiceRate(uint64_t Num, uint64_t DurationMilliseconds, const char* Unit = "B")
{
	char Buffer[32];

	if (DurationMilliseconds)
	{
		// Leave a little of 'Buffer' for the "Unit/s" suffix
		std::span<char> BufferSpan(Buffer, sizeof(Buffer) - 8);
		NiceNumToBuffer(Num * 1000 / DurationMilliseconds, BufferSpan);
	}
	else
	{
		strcpy(Buffer, "0");
	}

	strncat(Buffer, Unit, 4);
	strcat(Buffer, "/s");

	return Buffer;
}

//////////////////////////////////////////////////////////////////////////

template<Integral T>
std::optional<T>
ParseInt(const std::string_view& Input)
{
	T							 Out	= 0;
	const std::from_chars_result Result = std::from_chars(Input.data(), Input.data() + Input.size(), Out);
	if (Result.ec == std::errc::invalid_argument || Result.ec == std::errc::result_out_of_range)
	{
		return std::nullopt;
	}
	return Out;
}

//////////////////////////////////////////////////////////////////////////

constexpr uint32_t
HashStringDjb2(const std::string_view& InString)
{
	uint32_t HashValue = 5381;

	for (int CurChar : InString)
	{
		HashValue = HashValue * 33 + CurChar;
	}

	return HashValue;
}

constexpr uint32_t
HashStringAsLowerDjb2(const std::string_view& InString)
{
	uint32_t HashValue = 5381;

	for (uint8_t CurChar : InString)
	{
		CurChar -= ((CurChar - 'A') <= ('Z' - 'A')) * ('A' - 'a');	// this should be compiled into branchless logic
		HashValue = HashValue * 33 + CurChar;
	}

	return HashValue;
}

//////////////////////////////////////////////////////////////////////////

inline std::string
ToLower(const std::string_view& InString)
{
	std::string Out(InString);

	for (char& CurChar : Out)
	{
		CurChar -= (uint8_t(CurChar - 'A') <= ('Z' - 'A')) * ('A' - 'a');  // this should be compiled into branchless logic
	}

	return Out;
}

//////////////////////////////////////////////////////////////////////////

template<typename Fn>
uint32_t
ForEachStrTok(const std::string_view& Str, char Delim, Fn&& Func)
{
	const char* It	  = Str.data();
	const char* End	  = It + Str.length();
	uint32_t	Count = 0;

	while (It != End)
	{
		if (*It == Delim)
		{
			It++;
			continue;
		}

		std::string_view Remaining{It, size_t(ptrdiff_t(End - It))};
		size_t			 Idx = Remaining.find(Delim, 0);

		if (Idx == std::string_view::npos)
		{
			Idx = Remaining.size();
		}

		Count++;
		std::string_view Token{It, Idx};
		if (!Func(Token))
		{
			break;
		}

		It = It + Idx;
	}

	return Count;
}

//////////////////////////////////////////////////////////////////////////

inline std::string_view
ToString(bool Value)
{
	using namespace std::literals;
	if (Value)
	{
		return "true"sv;
	}
	return "false"sv;
}

//////////////////////////////////////////////////////////////////////////

inline int32_t
StrCaseCompare(const char* Lhs, const char* Rhs, int64_t Length = -1)
{
	// A helper for cross-platform case-insensitive string comparison.
#if ZEN_PLATFORM_WINDOWS
	return (Length < 0) ? _stricmp(Lhs, Rhs) : _strnicmp(Lhs, Rhs, size_t(Length));
#else
	return (Length < 0) ? strcasecmp(Lhs, Rhs) : strncasecmp(Lhs, Rhs, size_t(Length));
#endif
}

/**
 * @brief
 * Helper function to implement case sensitive spaceship operator for strings.
 * MacOS clang version we use does not implement <=> for std::string
 * @param Lhs string
 * @param Rhs string
 * @return std::strong_ordering indicating relationship between Lhs and Rhs
 */
inline auto
caseSensitiveCompareStrings(const std::string& Lhs, const std::string& Rhs)
{
	int r = Lhs.compare(Rhs);
	return r == 0 ? std::strong_ordering::equal : r < 0 ? std::strong_ordering::less : std::strong_ordering::greater;
}

//////////////////////////////////////////////////////////////////////////

/**
 * ASCII character bitset useful for fast and readable parsing
 *
 * Entirely constexpr. Works with both wide and narrow strings.
 *
 * Example use cases:
 *
 *   constexpr AsciiSet WhitespaceCharacters(" \v\f\t\r\n");
 *   bool bIsWhitespace = WhitespaceCharacters.Test(MyChar);
 *   const char* HelloWorld = AsciiSet::Skip("  \t\tHello world!", WhitespaceCharacters);
 *
 *   constexpr AsciiSet XmlEscapeChars("&<>\"'");
 *   check(AsciiSet::HasNone(EscapedXmlString, XmlEscapeChars));
 *
 *   constexpr AsciiSet Delimiters(".:;");
 *   const TCHAR* DelimiterOrEnd = AsciiSet::FindFirstOrEnd(PrefixedName, Delimiters);
 *   FString Prefix(PrefixedName, DelimiterOrEnd - PrefixedName);
 *
 *   constexpr AsciiSet Slashes("/\\");
 *   const TCHAR* SlashOrEnd = AsciiSet::FindLastOrEnd(PathName, Slashes);
 *   const TCHAR* FileName = *SlashOrEnd ? SlashOrEnd + 1 : PathName;
 */
class AsciiSet
{
public:
	template<typename CharType, int N>
	constexpr AsciiSet(const CharType (&Chars)[N]) : AsciiSet(StringToBitset(Chars))
	{
	}

	/** Returns true if a character is part of the set */
	template<typename CharType>
	constexpr inline bool Contains(CharType Char) const
	{
		using UnsignedCharType = typename std::make_unsigned<CharType>::type;

		return !!TestImpl((UnsignedCharType)Char);
	}

	/** Returns non-zero if a character is part of the set. Prefer Contains() to avoid VS2019 conversion warnings. */
	template<typename CharType>
	constexpr inline uint64_t Test(CharType Char) const
	{
		using UnsignedCharType = typename std::make_unsigned<CharType>::type;

		return TestImpl((UnsignedCharType)Char);
	}

	/** Create new set with specified character in it */
	constexpr inline AsciiSet operator+(char Char) const
	{
		using UnsignedCharType = typename std::make_unsigned<char>::type;

		InitData Bitset = {LoMask, HiMask};
		SetImpl(Bitset, (UnsignedCharType)Char);
		return AsciiSet(Bitset);
	}

	/** Create new set containing inverse set of characters - likely including null-terminator */
	constexpr inline AsciiSet operator~() const { return AsciiSet(~LoMask, ~HiMask); }

	////////// Algorithms for C strings //////////

	/** Find first character of string inside set or end pointer. Never returns null. */
	template<class CharType>
	static constexpr const CharType* FindFirstOrEnd(const CharType* Str, AsciiSet Set)
	{
		for (AsciiSet SetOrNil(Set.LoMask | NilMask, Set.HiMask); !SetOrNil.Test(*Str); ++Str)
			;

		return Str;
	}

	/** Find last character of string inside set or end pointer. Never returns null. */
	template<class CharType>
	static constexpr const CharType* FindLastOrEnd(const CharType* Str, AsciiSet Set)
	{
		const CharType* Last = FindFirstOrEnd(Str, Set);

		for (const CharType* It = Last; *It; It = FindFirstOrEnd(It + 1, Set))
		{
			Last = It;
		}

		return Last;
	}

	/** Find first character of string outside of set. Never returns null. */
	template<typename CharType>
	static constexpr const CharType* Skip(const CharType* Str, AsciiSet Set)
	{
		while (Set.Contains(*Str))
		{
			++Str;
		}

		return Str;
	}

	/** Test if string contains any character in set */
	template<typename CharType>
	static constexpr bool HasAny(const CharType* Str, AsciiSet Set)
	{
		return *FindFirstOrEnd(Str, Set) != '\0';
	}

	/** Test if string contains no character in set */
	template<typename CharType>
	static constexpr bool HasNone(const CharType* Str, AsciiSet Set)
	{
		return *FindFirstOrEnd(Str, Set) == '\0';
	}

	/** Test if string contains any character outside of set */
	template<typename CharType>
	static constexpr bool HasOnly(const CharType* Str, AsciiSet Set)
	{
		return *Skip(Str, Set) == '\0';
	}

	////////// Algorithms for string types like std::string_view and std::string //////////

	/** Get initial substring with all characters in set */
	template<class StringType>
	static constexpr StringType FindPrefixWith(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Forward, EInclude::Members, EKeep::Head>(Str, Set);
	}

	/** Get initial substring with no characters in set */
	template<class StringType>
	static constexpr StringType FindPrefixWithout(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Forward, EInclude::NonMembers, EKeep::Head>(Str, Set);
	}

	/** Trim initial characters in set */
	template<class StringType>
	static constexpr StringType TrimPrefixWith(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Forward, EInclude::Members, EKeep::Tail>(Str, Set);
	}

	/** Trim initial characters not in set */
	template<class StringType>
	static constexpr StringType TrimPrefixWithout(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Forward, EInclude::NonMembers, EKeep::Tail>(Str, Set);
	}

	/** Get trailing substring with all characters in set */
	template<class StringType>
	static constexpr StringType FindSuffixWith(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Reverse, EInclude::Members, EKeep::Tail>(Str, Set);
	}

	/** Get trailing substring with no characters in set */
	template<class StringType>
	static constexpr StringType FindSuffixWithout(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Reverse, EInclude::NonMembers, EKeep::Tail>(Str, Set);
	}

	/** Trim trailing characters in set */
	template<class StringType>
	static constexpr StringType TrimSuffixWith(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Reverse, EInclude::Members, EKeep::Head>(Str, Set);
	}

	/** Trim trailing characters not in set */
	template<class StringType>
	static constexpr StringType TrimSuffixWithout(const StringType& Str, AsciiSet Set)
	{
		return Scan<EDir::Reverse, EInclude::NonMembers, EKeep::Head>(Str, Set);
	}

	/** Test if string contains any character in set */
	template<class StringType>
	static constexpr bool HasAny(const StringType& Str, AsciiSet Set)
	{
		return !HasNone(Str, Set);
	}

	/** Test if string contains no character in set */
	template<class StringType>
	static constexpr bool HasNone(const StringType& Str, AsciiSet Set)
	{
		uint64_t Match = 0;
		for (auto Char : Str)
		{
			Match |= Set.Test(Char);
		}
		return Match == 0;
	}

	/** Test if string contains any character outside of set */
	template<class StringType>
	static constexpr bool HasOnly(const StringType& Str, AsciiSet Set)
	{
		auto End = Str.data() + Str.size();
		return FindFirst<EInclude::Members>(Set, Str.data(), End) == End;
	}

private:
	enum class EDir
	{
		Forward,
		Reverse
	};
	enum class EInclude
	{
		Members,
		NonMembers
	};
	enum class EKeep
	{
		Head,
		Tail
	};

	template<EInclude Include, typename CharType>
	static constexpr const CharType* FindFirst(AsciiSet Set, const CharType* It, const CharType* End)
	{
		for (; It != End && (Include == EInclude::Members) == !!Set.Test(*It); ++It)
			;
		return It;
	}

	template<EInclude Include, typename CharType>
	static constexpr const CharType* FindLast(AsciiSet Set, const CharType* It, const CharType* End)
	{
		for (; It != End && (Include == EInclude::Members) == !!Set.Test(*It); --It)
			;
		return It;
	}

	template<EDir Dir, EInclude Include, EKeep Keep, class StringType>
	static constexpr StringType Scan(const StringType& Str, AsciiSet Set)
	{
		auto Begin = Str.data();
		auto End   = Begin + Str.size();
		auto It	   = Dir == EDir::Forward ? FindFirst<Include>(Set, Begin, End) : FindLast<Include>(Set, End - 1, Begin - 1) + 1;

		return Keep == EKeep::Head ? StringType(Begin, static_cast<int32_t>(It - Begin)) : StringType(It, static_cast<int32_t>(End - It));
	}

	// Work-around for constexpr limitations
	struct InitData
	{
		uint64_t Lo, Hi;
	};
	static constexpr uint64_t NilMask = uint64_t(1) << '\0';

	static constexpr inline void SetImpl(InitData& Bitset, uint32_t Char)
	{
		uint64_t IsLo = uint64_t(0) - (Char >> 6 == 0);
		uint64_t IsHi = uint64_t(0) - (Char >> 6 == 1);
		uint64_t Bit  = uint64_t(1) << uint8_t(Char & 0x3f);

		Bitset.Lo |= Bit & IsLo;
		Bitset.Hi |= Bit & IsHi;
	}

	constexpr inline uint64_t TestImpl(uint32_t Char) const
	{
		uint64_t IsLo = uint64_t(0) - (Char >> 6 == 0);
		uint64_t IsHi = uint64_t(0) - (Char >> 6 == 1);
		uint64_t Bit  = uint64_t(1) << (Char & 0x3f);

		return (Bit & IsLo & LoMask) | (Bit & IsHi & HiMask);
	}

	template<typename CharType, int N>
	static constexpr InitData StringToBitset(const CharType (&Chars)[N])
	{
		using UnsignedCharType = typename std::make_unsigned<CharType>::type;

		InitData Bitset = {0, 0};
		for (int I = 0; I < N - 1; ++I)
		{
			SetImpl(Bitset, UnsignedCharType(Chars[I]));
		}

		return Bitset;
	}

	constexpr AsciiSet(InitData Bitset) : LoMask(Bitset.Lo), HiMask(Bitset.Hi) {}

	constexpr AsciiSet(uint64_t Lo, uint64_t Hi) : LoMask(Lo), HiMask(Hi) {}

	uint64_t LoMask, HiMask;
};

//////////////////////////////////////////////////////////////////////////

void string_forcelink();  // internal

}  // namespace zen
